定义类型的模块

  基于模块的程序设计趋向于以一个类型的所有数据为中心,在某个类型管理模块的控制之下工作。例如,如果我们希望有许多堆栈---而不是像前面那样只用一个由 Stack 模块提供的堆栈---我们就可能会定义一个堆栈管理器,它具有如下的界面:

    namespace Stack
    {
        struct Rep;                // 在另外某个地方定义堆栈的布局
        typedef Rep& stack;

        stack create();            // 做出一个新堆栈
        void destroy(stack s);     // 删除s

        void push(stack s, char c);// 将c压入s
        char pop(stack s);         // 弹出s
    }

声明

    struct Rep;

Rep 是一个类型的名字,但将这个类型留待以后再去定义。声明

    typedef Rep& stack;

确定名字 stack 是“对Rep的引用”。这里的想法是,堆栈由 Stack::stack 表示,而进一步的细节则对用户隐藏起来。

一个 Stack::stack 用起来很像一个内部类型的变量:

    struct Bad_pop { };

    void f()
    {
        Stack::stack s1 = Stack::create();    // 做出一个新堆栈
        Stack::stack s2 = Stack::create();    // 做出另一个新堆栈

        Stack::push(s1, 'c');
        Stack::push(s2, 'k');

        if(Stack::pop(s1) != 'c') throw Bad_pop();
        if(Stack::pop(s2) != 'k') throw Bad_pop();

        Stack::destroy(s1);
        Stack::destroy(s2);
    }

我们可以以几种不同的方式来实现这个堆栈。最重要的是,用户完全不必知道我们到底是怎么做的。只要我们能保持这个界面不改变,即使是决定重新实现 Stack,用户也不会受到任何影响。

某个实现可以是预先分配几个堆栈,而让 Stack::create() 递交出到某个尚未使用的堆栈的引用。而 Stack::destroy() 则将一个堆栈表示标记为未使用的,以使 Stack::create() 能够重新用它

    namespace Stack    // 表示
    {
        const int max_size = 200;

        struct Rep
        {
            char v[max_size];
            int top;
        };

        const int max = 16;    // 最大堆栈数
        Rep stacks[max];       // 预分配的堆栈表示
        bool used[max];        // 如果stacks[i]在使用, 则used[i]为真

        typedef Rep& stack;
    }

    void Stack::push(Stack::stack s, char c) { /* 检查s的上溢并压入c */ }
    char Stack::pop(Stack::stack s) { /* 检查s的下溢并弹出 */ }
    Stack::stack Stack::create()
    {
        // 找一个未使用的Rep, 将它标记为已使用的,将它初始化,并返回它的引用
    }
    void Stack::destroy(Stack::stack s) { /* 标记s为未使用的 */ }

我们在这里做的就是围绕着一个表示类型包装起一组界面函数。所做的结果得到的“堆栈类型”具有怎样的行为,部分在于我们如何定义这些界面函数,部分在于我们如何将Stack的表示展示给它的用户,部分在于这个表示本身的设计情况。

这种做法常常不是最理想的。一个重要问题就是,这里给用户提供了一个“假类型”的表示,它可以因为表示类型不同而出现很大变化---而与此同时,又应该将用户隔离于这一表示之外。例如,如果我们选择采用另外一种更精致的数据结构去表示一个堆栈,那么Stack::stack 的表示类型。

更根本的问题是,通过模块实现的这种用户定义类型,它们所提供的对这种类型的访问,在行为上,并不像内部的类型。它们得到的支持也与内部类型不同,而且实际上更少一些。例如,一个 Stack::Rep 能被使用的期间由 Stack::create()Stack::destroy() 控制,而不是由普遍性的语言规则控制。


代码 (edit by shenjun):

    namespace Stack        // 界面
    {
        struct Rep;                // 声明
        typedef Rep& stack;

        stack create();
        void destroy(stack s);

        void push(stack s, char c);
        char pop(stack s);
        void show(stack s);
    }

    struct Bad_pop{};
    struct Bad_push{};

    struct Overflow{};

    void f()
    {
        Stack::stack s1 = Stack::create();
        Stack::stack s2 = Stack::create();

        Stack::push(s1, 'c');
        Stack::push(s2, 'k');

        /*if (Stack::pop(s1) != 'c') throw Bad_pop();
        if (Stack::pop(s2) != 'k') throw Bad_pop();*/

        Stack::push(s1, 'a');
        Stack::push(s1, 'b');

        Stack::push(s2, 'e');
        Stack::push(s2, 'f');
        Stack::push(s2, 'k');

        Stack::pop(s2);

        Stack::show(s1);
        Stack::show(s2);

        Stack::destroy(s1);
        Stack::destroy(s2);
    }

    namespace Stack        // 实现
    {
        const int max_size = 200;

        struct Rep        // 定义
        {
            char v[max_size];
            int top;
            int index;    // 保存当前堆栈在数组中的索引值,以便根据该索引值 destroy
        };

        const int max = 16;            // 最大堆栈数
        Rep stacks[max];            // 预分配的堆栈
        bool used[max];                // 如果stack[i]在使用,则used[i]为true
    }

    void Stack::push(Stack::stack s, char c) 
    {
        /* 检查s的上溢并压入c */
        if (s.top == max_size) throw Bad_push();

        s.v[s.top] = c;
        ++s.top;
    }

    char Stack::pop(Stack::stack s)
    {
        /* 检查s的下溢并弹出 */
        if (s.top == 0) throw Bad_pop();
        --s.top;
        return s.v[s.top];
    }

    Stack::stack Stack::create()
    {
        // 找到一个未使用的Rep,将它标记为已使用的,将它初始化,并返回它的引用
        int index = 0;
        while (Stack::used[index] && index < Stack::max)
        {
            index++;
        }

        if (index == Stack::max) throw Overflow();

        Stack::used[index] = true;        // 使用该堆栈,标记为true
        Stack::stacks[index].top = 0;
        Stack::stacks[index].index = index;
        return Stack::stacks[index];
    }

    void Stack::destroy(Stack::stack s)
    {
        // 标记s为未使用的
        Stack::used[s.index] = false;
    }

    void Stack::show(Stack::stack s)
    {
        for (size_t i = 0; i < s.top; i++)
        {
            printf("%c", s.v[i]);
        }
        printf("\n");
    }

🔚